package com.gzx.spring.apis.utils;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.digest.DigestAlgorithm;
import com.gzx.spring.apis.enums.SignType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class SignUtil {

	private static Map<String, Sign> signs = new ConcurrentHashMap<>();
	private static HashSet<String> ignoreField = new HashSet<>();
	
	static {
		ignoreField.add("certFile");
		ignoreField.add("incomeFile");
		ignoreField.add("otherFile");
		ignoreField.add("authoFile");
		ignoreField.add("workFile");
		ignoreField.add("assetsFile");
		ignoreField.add("identFile");
		ignoreField.add("idCardFile");
		ignoreField.add("otherAuthFile");
		ignoreField.add("caFile");
		
		ignoreField.add("identPicPath");
		ignoreField.add("assPicPath");
		ignoreField.add("contractPicPath");
		ignoreField.add("proPicPath");
		ignoreField.add("agrPicPath");
		
	}
	
	public static String sign(Map<String, String> map, String key) throws Exception {
		StringBuilder stringBuilder = new StringBuilder(sign(map));
		stringBuilder.append("&").append("key=").append(key);
		return DigestUtils.md5Hex(stringBuilder.toString().getBytes("UTF-8")).toUpperCase();
	}
	
	public static String sign(Map<String, String> map) {
		StringBuilder stringBuilder = new StringBuilder("");
		for (Entry<String, String> entry : map.entrySet()) {
			if (StringUtils.isNotBlank(entry.getKey()) && StringUtils.isNotBlank(entry.getValue())) {
				stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
			}
		}
		String str = stringBuilder.toString();
		return StringUtils.substring(str, 0, str.length() - 1);
	}
	
	public static String sign(String key, String value) {
		StringBuilder stringBuilder = new StringBuilder("");
		stringBuilder.append(key).append("=").append(value);
		return stringBuilder.toString();
	}

	/**
	 * 初始化签名算法
	 * 
	 * @Title: initSign
	 * @Description: 初始化签名器，单例
	 * @param merchantId
	 * @param privateKeyBase64
	 * @param publicKeyBase64
	 */
	public static void initSign(String merchantId, String privateKeyBase64, String publicKeyBase64) {
		if (!signs.containsKey(merchantId)) {
			Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, privateKeyBase64, publicKeyBase64);
			signs.put(merchantId, sign);
		}
	}

	private static void clearIgnoreField(Map<String, Object> params) {
		ignoreField.stream().forEach(key -> params.remove(key));
	}

	/**
	 * 签名剔除了文件，推荐使用
	 * 
	 * @Title: getRsaSignature
	 * @param params
	 * @param merchantId
	 * @return
	 */
	public static String getRsa1Signature(Map<String, Object> params, String merchantId) {
		params.remove("signature");

		clearIgnoreField(params);

		params.put("signType", SignType.RSA1.getType());

		String joinStr = MapUtil.sortJoin(params, StrUtil.EMPTY, StrUtil.EMPTY, true, "");
		log.info(joinStr);
		Sign sign = signs.get(merchantId);
		if (sign == null) {
			throw new RuntimeException("签名算法对象尚未初始化");
		}
		byte[] signByte = sign.sign(joinStr.getBytes());

		return Base64.encode(signByte);
	}

	/**
	 * MD5方式签名
	 * 
	 * @Title: getMD5Signature
	 * @Description:
	 * @param params
	 * @param merchantId
	 * @return
	 */
	public static String getMd5Signature(Map<String, Object> params, String md5Key) {
		clearIgnoreField(params);
		params.put("signType", SignType.MD5.getType());
		String md5Field = sortJoin(params, StrUtil.EMPTY, StrUtil.EMPTY,true, md5Key);
		log.info("原始签名串：{}",md5Field);
		String signBase64 = SecureUtil.md5(md5Field);
		return signBase64;
	}
	
	public static void main(String[] args) {
		//System.out.println(SecureUtil.md5("assureContractAssureContract(assureContractType=01, assureManner=2, assureContractMoney=1500, assureBeginDate=2022-01-16, assureFinishDate=2022-04-17, depositsRatio=null, beAssureManner=null, compensationRatio=null, rates=0, annualizedRates=null, identPicpath=null, assPicPath=null, contractPicPath=null, proPicPath=null, agrPicPath=null, identPicPathType=.JPG, assPicPathType=null, contractPicPathType=null, proPicPathType=null, agrPicPathType=.PDF)assureUploadAssureUpload(businessNumber=1483022859093573634, contractNumber=1483022859093573634, isAssureType=2, isAssureName=dIDBY0CZtMU=, isAssureCreditType=0, isAssureCreditCode=PivsMuhkMgaqW0xAAO2OKbg7kFCJgQ9x, businessYearMonth=2022-01-16)businessType07merchantId10000019realInBulgariaRealInBulgaria(assureContractStatus=1, dutyRelieveDate=null, balanceInBulgaria=1500, balanceChangeDate=2022-01-16)requestNo1483022859093573633signTypemd52eo6qhb53esrp0zli2jv"));
	}

	/**
	 * 验签
	 * @Title: verify
	 * @return
	 */
	public static boolean verify(Map<String, Object> resultMap, String signature, String merchantId) {
		String resultStr = MapUtil.sortJoin(resultMap, StrUtil.EMPTY, StrUtil.EMPTY, true, "");
		Sign sign = signs.get(merchantId);
		if (sign == null) {
			throw new RuntimeException("签名算法对象尚未初始化");
		}
		return sign.verify(resultStr.getBytes(), Base64.decode(signature));
	}
	
	/**
	 * 验签
	 * @Title: verify
	 * @return
	 */
	public static boolean md5Verify(Map<String, Object> resultMap, String signature,String md5Key) {
		String signBase64 = SecureUtil.signParams(DigestAlgorithm.MD5, resultMap, StrUtil.EMPTY, StrUtil.EMPTY, true,
				md5Key);
		return signature.equals(signBase64);
	}
	
	
	
	/**
	 * 排序已有Map，Key有序的Map
	 *
	 * @param <K>        key的类型
	 * @param <V>        value的类型
	 * @param map        Map，为null返回null
	 * @param comparator Key比较器
	 * @return TreeMap，map为null返回null
	 * @see #newTreeMap(Map, Comparator)
	 * @since 4.0.1
	 */
	public static <K, V> TreeMap<K, V> sort(Map<K, V> map, Comparator<? super K> comparator) {
		if (null == map) {
			return null;
		}

		TreeMap<K, V> result;
		if (map instanceof TreeMap) {
			// 已经是可排序Map，此时只有比较器一致才返回原map
			result = (TreeMap<K, V>) map;
			if (null == comparator || comparator.equals(result.comparator())) {
				return result;
			}
		} else {
			result = newTreeMap(map, comparator);
		}

		return result;
	}
	
	/**
	 * 新建TreeMap，Key有序的Map
	 *
	 * @param <K>        key的类型
	 * @param <V>        value的类型
	 * @param map        Map
	 * @param comparator Key比较器
	 * @return TreeMap
	 * @since 3.2.3
	 */
	public static <K, V> TreeMap<K, V> newTreeMap(Map<K, V> map, Comparator<? super K> comparator) {
		final TreeMap<K, V> treeMap = new TreeMap<>(comparator);
		if (false == isEmpty(map)) {
			treeMap.putAll(map);
		}
		return treeMap;
	}
	
	/**
	 * Map是否为空
	 *
	 * @param map 集合
	 * @return 是否为空
	 */
	public static boolean isEmpty(Map<?, ?> map) {
		return null == map || map.isEmpty();
	}
	
	/**
	 * 根据参数排序后拼接为字符串，常用于签名
	 *
	 * @param params            参数
	 * @param separator         entry之间的连接符
	 * @param keyValueSeparator kv之间的连接符
	 * @param isIgnoreNull      是否忽略null的键和值
	 * @param otherParams       其它附加参数字符串（例如密钥）
	 * @return 签名字符串
	 * @since 5.0.4
	 */
	public static String sortJoin(Map<?, ?> params, String separator, String keyValueSeparator, boolean isIgnoreNull,
								  String... otherParams) {
		return join(sort(params,null), separator, keyValueSeparator, isIgnoreNull, otherParams);
	}
	
	
	/**
	 * 将map转成字符串
	 *
	 * @param <K>               键类型
	 * @param <V>               值类型
	 * @param map               Map，为空返回otherParams拼接
	 * @param separator         entry之间的连接符
	 * @param keyValueSeparator kv之间的连接符
	 * @param isIgnoreNull      是否忽略null的键和值
	 * @param otherParams       其它附加参数字符串（例如密钥）
	 * @return 连接后的字符串，map和otherParams为空返回""
	 * @since 3.1.1
	 */
	public static <K, V> String join(Map<K, V> map, String separator, String keyValueSeparator, boolean isIgnoreNull, String... otherParams) {
		final StringBuilder strBuilder = StrUtil.builder();
		boolean isFirst = true;
		if (isNotEmpty(map)) {
			for (Entry<K, V> entry : map.entrySet()) {
				if (false == isIgnoreNull || entry.getKey() != null && entry.getValue() != null) {
					if (isFirst) {
						isFirst = false;
					} else {
						strBuilder.append(separator);
					}
					strBuilder.append(Convert.toStr(entry.getKey())).append(keyValueSeparator).append(Convert.toStr(entry.getValue()));
				}
			}
		}
		// 补充其它字符串到末尾，默认无分隔符
		if (ArrayUtil.isNotEmpty(otherParams)) {
			for (String otherParam : otherParams) {
				strBuilder.append(otherParam);
			}
		}
		return strBuilder.toString();
	}
	/**
	 * Map是否为非空
	 *
	 * @param map 集合
	 * @return 是否为非空
	 */
	public static boolean isNotEmpty(Map<?, ?> map) {
		return null != map && false == map.isEmpty();
	}
}
